package org.kirhgoff.mathbot;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class MathModule {
	
	private static final int OWNER = 0;
	private static final int POPULATION = 1;
	private Gene gene = null;
	
	public MathModule(Gene gene) {
		this.gene = gene;
	}

	/**
	 * How much ships we can spare to attack from planet
	 */
	public int [] populationAfterLastFleetArrived(World world, PlanetInfo planet) {
		List<FleetInfo> targetingFleets = world.getAllTargetingFleets (planet);
		sortByRemainingTurns(targetingFleets);
		int population = planet.population();
		int owner = planet.owner();
		int time = 0;
		for (FleetInfo fleet: targetingFleets) {
			time = fleet.getRemainingTurns() - time;
			population += time*planet.growthRate();
			if (planet.owner() != fleet.owner()) {
				population -= fleet.size();
			}
			else {
				population += fleet.size ();
			}
			if (population <= 0) {
				population = -population;
				owner = fleet.owner();
			}
		}
		return new int [] {owner, population};
	}

	public int howMuchIsRequiredToCapture(World world, PlanetInfo target, PlanetInfo attacker) {
		long startTime = System.currentTimeMillis();
		
		List<FleetInfo> targetingFleets = world.getAllTargetingFleets (target);
		int myDistance = World.distance(attacker, target);
		sortByRemainingTurns(targetingFleets);

		int population = target.population();
		int owner = target.owner();
		int ships = 0;
		int time = 0;
		boolean myFleetArrived = false;
		//TODO incorrect
		int i = 0;
		
		for (; i < targetingFleets.size (); i ++) {
			FleetInfo fleet = targetingFleets.get(i);
			
			if (myDistance < fleet.getRemainingTurns() && !myFleetArrived) {//we found a moment in time when our fleet arrives
				if (owner != World.OWNER_ME) {
					ships += population + 1;
				}
				else {
					ships = 0;
				}
				myFleetArrived = true;
			}
			
			if (owner != World.OWNER_NEUTRAL) {
				population += (fleet.getRemainingTurns() - time)* target.growthRate();
			}
			
			time = fleet.getRemainingTurns();

			if (owner != fleet.owner()) {
				population -= fleet.size ();
			}
			else {
				population += fleet.size();
			}
			
			if (population < 0) {
				owner = fleet.owner();
				population = -population;
			}
			
			if (myFleetArrived && owner != World.OWNER_ME) {
				ships += population + 1;
				population = 1;
				owner = World.OWNER_ME;
			}
		}

		//it could happen in two cases:
		//a) no fleets target the planet at all
		//b) my fleet is the last fleet arrived
		if (ships == 0) {
			if (owner == World.OWNER_NEUTRAL) {
				ships = population + 1;
			} else if (owner == World.OWNER_ENEMY) {
				ships = population + target.growthRate() * myDistance + 1;
			} 
		}
		
		LOGGER.info("------------------\n" +
				"MATH howMuchIsRequiredToCapture=" + ships + "\ntarget=" + target 
				+ "\nattacker=" + attacker + "\ntargetingFleets=" +targetingFleets
				+ "\ntimeRequired=" + (System.currentTimeMillis() - startTime) + " ms" +
						"\n----------------------------");
		return ships;
	}

	/**
	 * Find the best target for attack from this planet
	 */
	public PlanetInfo getBestTargetForAttackFrom(World world, final PlanetInfo planet) {
		long startedAt = System.currentTimeMillis();
		PlanetInfo result = planet;
		List<PlanetInfo>  rankedPlanets = world.getHighRanked (planet, new Rank () {//Is this a real life?
				public double rank (World world, PlanetInfo anyPlanet) {
					return getTargetRank(world, planet, anyPlanet);
				}

		});
		//LOGGER.info("Planets: " + rankedPlanets);
		
		int index  = 0;
		if (rankedPlanets.get(0).id () == planet.id ()) {			
			int ownerPopulation[] = populationAfterLastFleetArrived (world, planet);
			if (ownerPopulation [OWNER] != World.OWNER_ME) {
				//if we already dead, attack myself
				return planet;
			}
			else {
				int oldForce = planet.force ();
				if (oldForce > ownerPopulation [POPULATION] + 1) {
					planet.setForce(oldForce - ownerPopulation [POPULATION] - 1);
				}
				index = 1;
				
			}
		}
		while (index < rankedPlanets.size ()) {
			PlanetInfo planetToAttack = rankedPlanets.get(index);
			if (!planetToAttack.coveredByAttack()) {
				result = rankedPlanets.get(index);
				break;
			}
//			else {
//				LOGGER.info ("Already covered: " + !planetToAttack.coveredByAttack() + ", next: " + planetToAttack.coveredByAttack());
//			}
			index ++;
		}
		LOGGER.info ("getBestTargetToAttack time=" + (System.currentTimeMillis() - startedAt) + "ms from=" + planet + " target=" + result);
		return result;
	}	
	
	//======================== MAIN FORMULAE ===================
	
	private double getTargetRank(World world, final PlanetInfo attacker, PlanetInfo target) {
		final double growthRatio = growthRatio (world, target); //the bigger growth the better
		final double populationRatio = populationRatio (world, target); //less populized the more important
		final double threat = threat (world, target); 
		final double ownershipRatio = ownershipRatio (world, target);
		double closenessToMyPlanets = closenessToPlanets (world, target, world.getMyPlanets());
		double closenessToEnemy = closenessToPlanets (world, target, world.getEnemyPlanets ());
		double closenessToSinglePlanet = closenessToSinglePlanet (world, target, attacker);
		
		double [] values = new double [] {
				gene.getGrowthRateWeight () * growthRatio,
				closenessToSinglePlanet,
				gene.getPopulationWeight () * populationRatio,
				//ownershipRatio,
				gene.getThreatWeight () * threat,
				//gene.getClosenessToMeWeight () * closenessToMyPlanets
				//gene.getClosenessToEnemyWeight () * closenessToEnemy,
		};
		
		double result = 0;
		for (int i = 0; i < values.length; i++) {
			result += values [i];
		}

		return result/values.length;
	}

	//============== UNDERLYING PARAMETERS ======
	private double populationRatio(World world, PlanetInfo planet) {
		double result = 1 - ((double)planet.population())/ world.maxPopulation ();
		return result;
	}
	
	private double closenessToSinglePlanet(World world, PlanetInfo target,
			PlanetInfo attacker) {
		int max = world.maxWorldDistance (attacker);
		int distance = World.distance(target, attacker);
		
		return 1 - ((double)distance)/max;
	}

	//TODO optimize
	private double closenessToPlanets(World world, PlanetInfo planet, List<PlanetInfo> planets) {
		int minDistance = world.minDistance (planet, planets);
		int maxDistance = world.maxWorldDistance (planet);
		double result = 1 - ((double)minDistance)/maxDistance;
		return result;
	}

	private double ownershipRatio(World world, PlanetInfo target) {
		double [] defendAndConquer = new double [] {gene.getDefendNeutral (), gene.getDefendMine (), gene.getDefendEnemy ()};
		double [] crashHim = new double [] {gene.getCrashNeutral (), gene.getCrashMine (), gene.getCrashEnemy ()};
		boolean attack = world.overallEnemyForce()/world.myOverallForce() < gene.getDefendCrashThreshold ();
		double result = 0;
		if (attack) {
			result = crashHim [target.owner()];
		}
		else {
			result = defendAndConquer [target.owner()];
		}
		return result;
	}

	private double growthRatio(World world, PlanetInfo planet) {
		int maxGrowthRate = world.maxGrowthRate ();
		double result = planet.growthRate()/maxGrowthRate;
		return result;
	}
	
	private double threat(World world, PlanetInfo planet) {
		int [] minMax = getThreatMinMax(world);
		int min = minMax[0];
		int max = minMax [1];
		int population = populationAfterLastFleetArrived(world, planet) [POPULATION];
		return ((double)(max - population))/(max - min); //normalize
	}

	//TODO optimize
	/**
	 * Returns array [future owner of the planet, future population (minus value if the planet has been captured)]
	 */
	private int [] getThreatMinMax(World world) {
		int smallest = Integer.MAX_VALUE;
		int biggest = Integer.MIN_VALUE;
		for (PlanetInfo planet : world.getAllPlanets()) {
			int [] ownerPop = populationAfterLastFleetArrived(world, planet);
			if (ownerPop [OWNER] != World.OWNER_ENEMY) {
				continue;
			}
			int threat = ownerPop [POPULATION];
			if (threat > biggest) {
				biggest = threat;
			}
			if (threat < smallest) {
				smallest = threat;
			}
		}
		return new int [] {smallest, biggest};
	}

	private void sortByRemainingTurns(List<FleetInfo> targetingFleets) {
		Collections.sort (targetingFleets, new Comparator<FleetInfo>() {
			public int compare(FleetInfo first, FleetInfo second) {
				return first.getRemainingTurns() - second.getRemainingTurns();
			}
		});
	}

	
}
